'use strict';

var Xsql = require('./xsql.js');
var Xctrl = require('./xctrl.js');
var multer = require('multer');
const path = require('path');

const v8 = require('v8'),
  os = require('os');

//define class
class Xapi {

  constructor(args, mysqlPool, app) {

    this.config = args;
    this.mysql = new Xsql(args, mysqlPool)
    this.app = app;
    this.ctrls = [];

    /**************** START : multer ****************/
    this.storage = multer.diskStorage({
      destination: function (req, file, cb) {
        cb(null, process.cwd())
      },
      filename: function (req, file, cb) {
        console.log(file);
        cb(null, Date.now() + '-' + file.originalname)
      }
    })

    this.upload = multer({storage: this.storage})
    /**************** END : multer ****************/


  }


  init(cbk) {

    this.mysql.init((err, results) => {

      this.app.use(this.urlMiddleware.bind(this))
      let stat = this.setupRoutes()
      this.app.use(this.errorMiddleware.bind(this))
      cbk(err, stat)

    })

  }


  urlMiddleware(req, res, next) {

    // get only request url from originalUrl
    let justUrl = req.originalUrl.split('?')[0]
    let pathSplit = []

    // split by apiPrefix
    let apiSuffix = justUrl.split(this.config.apiPrefix)

    if (apiSuffix.length === 2) {
      // split by /
      pathSplit = apiSuffix[1].split('/')
      if (pathSplit.length) {
        if (pathSplit.length >= 3) {
          // handle for relational routes
          req.app.locals._parentTable = pathSplit[0]
          req.app.locals._childTable = pathSplit[2]
        } else {
          // handles rest of routes
          req.app.locals._tableName = pathSplit[0]
        }

      }
    }

    next();

  }


  errorMiddleware(err, req, res, next) {

    if (err && err.code)
      res.status(400).json({error: err});
    else if (err && err.message)
      res.status(500).json({error: 'Internal server error : ' + err.message});
    else
      res.status(500).json({error: 'Internal server error : ' + err});

    next(err);

  }

  asyncMiddleware(fn) {
    return (req, res, next) => {
      Promise.resolve(fn(req, res, next))
        .catch((err) => {
          next(err);
        });
    }
  }

  root(req, res) {

    let routes = [];
    routes = this.mysql.getSchemaRoutes(false, req.protocol + '://' + req.get('host') + this.config.apiPrefix);
    routes = routes.concat(this.mysql.globalRoutesPrint(req.protocol + '://' + req.get('host') + this.config.apiPrefix))
    res.json(routes)

  }

  setupRoutes() {

    let stat = {}
    stat.tables = 0
    stat.apis = 0

    // console.log('this.config while setting up routes', this.config);

    // show routes for database schema
    this.app.get('/', this.asyncMiddleware(this.root.bind(this)))

    // show all resouces
    this.app.route(this.config.apiPrefix + 'tables')
      .get(this.asyncMiddleware(this.tables.bind(this)));

    this.app.route(this.config.apiPrefix + 'xjoin')
      .get(this.asyncMiddleware(this.xjoin.bind(this)));

    stat.apis += 3;


    /**************** START : setup routes for each table ****************/
    let resources = [];
    resources = this.mysql.getSchemaRoutes(true, this.config.apiPrefix);

    stat.tables += resources.length

    // iterate over each resource
    for (var j = 0; j < resources.length; ++j) {


      let resourceCtrl = new Xctrl(this.app, this.mysql)
      this.ctrls.push(resourceCtrl);

      let routes = resources[j]['routes'];

      stat.apis += resources[j]['routes'].length

      // iterate over each routes in resource and map function
      for (var i = 0; i < routes.length; ++i) {

        switch (routes[i]['routeType']) {

          case 'list':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.list.bind(resourceCtrl)));
            break;

          case 'findOne':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.findOne.bind(resourceCtrl)));
            break;

          case 'create':
            if (!this.config.readOnly)
              this.app.route(routes[i]['routeUrl'])
                .post(this.asyncMiddleware(resourceCtrl.create.bind(resourceCtrl)));
            break;

          case 'read':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.read.bind(resourceCtrl)));
            break;

          case 'bulkInsert':
            if (!this.config.readOnly) {
              this.app.route(routes[i]['routeUrl'])
                .post(this.asyncMiddleware(resourceCtrl.bulkInsert.bind(resourceCtrl)));
            }
            break;

          case 'bulkRead':
            if (!this.config.readOnly){
              this.app.route(routes[i]['routeUrl'])
                .get(this.asyncMiddleware(resourceCtrl.bulkRead.bind(resourceCtrl)));
            } else {
              stat.apis--;
            }
            break;

          case 'bulkDelete':
            if (!this.config.readOnly){
              this.app.route(routes[i]['routeUrl'])
                .delete(this.asyncMiddleware(resourceCtrl.bulkDelete.bind(resourceCtrl)));
            } else {
              stat.apis--;
            }
            break;

          case 'patch':
            if (!this.config.readOnly){
              this.app.route(routes[i]['routeUrl'])
                .patch(this.asyncMiddleware(resourceCtrl.patch.bind(resourceCtrl)));
            } else {
              stat.apis--;
            }
            break;

          case 'update':
            if (!this.config.readOnly){
              this.app.route(routes[i]['routeUrl'])
                .put(this.asyncMiddleware(resourceCtrl.update.bind(resourceCtrl)));
            } else {
              stat.apis--;
            }
            break;

          case 'delete':
            if (!this.config.readOnly){
              this.app.route(routes[i]['routeUrl'])
                .delete(this.asyncMiddleware(resourceCtrl.delete.bind(resourceCtrl)));
            } else {
              stat.apis--;
            }
            break;

          case 'exists':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.exists.bind(resourceCtrl)));
            break;

          case 'count':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.count.bind(resourceCtrl)));
            break;

          case 'distinct':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.distinct.bind(resourceCtrl)));
            break;

          case 'describe':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(this.tableDescribe.bind(this)));
            break;

          case 'relational':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.nestedList.bind(resourceCtrl)));
            break;

          case 'groupby':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.groupBy.bind(resourceCtrl)));
            break;

          case 'ugroupby':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.ugroupby.bind(resourceCtrl)));
            break;

          case 'chart':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.chart.bind(resourceCtrl)));
            break;

          case 'autoChart':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.autoChart.bind(resourceCtrl)));
            break;

          case 'aggregate':
            this.app.route(routes[i]['routeUrl'])
              .get(this.asyncMiddleware(resourceCtrl.aggregate.bind(resourceCtrl)));
            break;

        }
      }
    }
    /**************** END : setup routes for each table ****************/


    if (this.config.dynamic === 1 && !this.config.readOnly) {

      this.app.route('/dynamic*')
        .post(this.asyncMiddleware(this.runQuery.bind(this)));

      /**************** START : multer routes ****************/
      this.app.post('/upload', this.upload.single('file'), this.uploadFile.bind(this));
      this.app.post('/uploads', this.upload.array('files', 10), this.uploadFiles.bind(this));
      this.app.get('/download', this.downloadFile.bind(this));
      /**************** END : multer routes ****************/

      stat.apis += 4;

    }

    /**************** START : health and version ****************/
    this.app.get('/_health', this.asyncMiddleware(this.health.bind(this)));
    this.app.get('/_version', this.asyncMiddleware(this.version.bind(this)));
    stat.apis += 2;
    /**************** END : health and version ****************/


    let statStr = '     Generated: ' + stat.apis + ' REST APIs for ' + stat.tables + ' tables '

    console.log(' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ');
    console.log('                                                            ');
    console.log('          Database              :    %s', this.config.database);
    console.log('          Number of Tables      :    %s', stat.tables);
    console.log('                                                            ');
    console.log('          REST APIs Generated   :    %s'.green.bold, stat.apis);
    console.log('                                                            ');

    return stat

  }

  async xjoin(req, res) {

    let obj = {}

    obj.query = '';
    obj.params = [];

    this.mysql.prepareJoinQuery(req, res, obj)

    //console.log(obj);
    if (obj.query.length) {
      let results = await this.mysql.exec(obj.query, obj.params)
      res.status(200).json(results)
    } else {
      res.status(400).json({err: 'Invalid Xjoin request'});
    }

  }

  async tableDescribe(req, res) {

    let query = 'describe ??';
    let params = [req.app.locals._tableName];

    let results = await this.mysql.exec(query, params);
    res.status(200).json(results);

  }

  async tables(req, res) {

    let query = 'SELECT table_name AS resource FROM information_schema.tables WHERE table_schema = ? ';
    let params = [this.config.database];

    if (Object.keys(this.config.ignoreTables).length > 0) {
      query += 'and table_name not in (?)'
      params.push(Object.keys(this.config.ignoreTables))
    }

    let results = await this.mysql.exec(query, params)

    res.status(200).json(results)
  }

  async runQuery(req, res) {

    let query = req.body.query;
    let params = req.body.params;

    let results = await this.mysql.exec(query, params);
    res.status(200).json(results);


  }


  /**************** START : files related ****************/
  downloadFile(req, res) {
    let file = path.join(process.cwd(), req.query.name);
    res.download(file);
  }

  uploadFile(req, res) {

    if (req.file) {
      console.log(req.file.path);
      res.end(req.file.path);
    } else {
      res.end('upload failed');
    }
  }

  uploadFiles(req, res) {

    if (!req.files || req.files.length === 0) {
      res.end('upload failed')
    } else {
      let files = [];
      for (let i = 0; i < req.files.length; ++i) {
        files.push(req.files[i].path);
      }

      res.end(files.toString());
    }

  }

  /**************** END : files related ****************/


  /**************** START : health and version ****************/

  async getMysqlUptime() {
    let v = await this.mysql.exec('SHOW GLOBAL STATUS LIKE \'Uptime\';', []);
    return v[0]['Value'];
  }

  async getMysqlHealth() {
    let v = await this.mysql.exec('select version() as version', []);
    return v[0]['version'];
  }

  async health(req, res) {

    let status = {};
    status['process_uptime'] = process.uptime();
    status['mysql_uptime'] = await this.getMysqlUptime();

    if (Object.keys(req.query).length) {
      status['process_memory_usage'] = process.memoryUsage();
      status['os_total_memory'] = os.totalmem();
      status['os_free_memory'] = os.freemem();
      status['os_load_average'] = os.loadavg();
      status['v8_heap_statistics'] = v8.getHeapStatistics();
    }

    res.json(status);

  }

  async version(req, res) {

    let version = {};

    version['Xmysql'] = this.app.get('version');
    version['mysql'] = await this.getMysqlHealth();
    version['node'] = process.versions.node;
    res.json(version);

  }

  /**************** END : health and version ****************/

}


//expose class
module.exports = Xapi;

